package com.sky.Task;

import cn.mybatis.mp.core.sql.executor.chain.QueryChain;
import com.sky.config.ThreadPoolProperties;
import com.sky.constant.UserActionConstant;
import com.sky.entity.*;
import com.sky.mapper.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * 图书热度分数更新任务
 */
@Slf4j
@Component
public class PopularityScoreTask {

    @Autowired
    private BooksMapper booksMapper;

    @Autowired
    private UserBookRatingsMapper userBookRatingsMapper;

    @Autowired
    private ThreadPoolProperties threadPoolProperties;

    @Autowired
    private UseractionsMapper useractionsMapper;

    // 使用配置创建线程池
    private ExecutorService createExecutorService() {
        return new ThreadPoolExecutor(
                threadPoolProperties.getCorePoolSize(),
                threadPoolProperties.getMaxPoolSize(),
                threadPoolProperties.getKeepAliveTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(threadPoolProperties.getQueueCapacity()),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }

    @PostConstruct
    public void init() {
        executorService = createExecutorService();
    }

    @PreDestroy
    public void destroy() {
        if (executorService != null) {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
            }
        }
    }

    // 修改字段定义
    private ExecutorService executorService;

    /**
     * 每天凌晨2点执行图书热度更新任务
     */
    @Scheduled(cron = "0 0 2 * * ?")
    @Transactional
    public void updateBooksPopularityScore() {
        log.info("开始执行图书热度更新任务...");

        try {
            // 获取所有图书
            List<Books> allBooks = QueryChain.of(booksMapper)
                    .list();

            // 创建计数器
            CountDownLatch latch = new CountDownLatch(allBooks.size());
            // 创建结果收集器
            List<Future<UpdateResult>> futures = new ArrayList<>();

            // 并行处理每本图书
            for (Books book : allBooks) {
                Future<UpdateResult> future = executorService.submit(() -> {
                    try {
                        double newScore = calculateBookScore(book);
                        // 更新图书热度分数
                        Books updateBook = new Books();
                        updateBook.setBookId(book.getBookId());
                        updateBook.setPopularityScore(newScore);
                        booksMapper.update(updateBook);

                        return new UpdateResult(book.getBookId(), newScore, true, null);
                    } catch (Exception e) {
                        log.error("更新图书热度分数失败 - 图书ID: {}", book.getBookId(), e);
                        return new UpdateResult(book.getBookId(), 0.0, false, e.getMessage());
                    } finally {
                        latch.countDown();
                    }
                });
                futures.add(future);
            }

            // 等待所有任务完成
            latch.await(30, TimeUnit.MINUTES);  // 设置最大等待时间

            // 处理结果
            int successCount = 0;
            int failCount = 0;
            for (Future<UpdateResult> future : futures) {
                try {
                    UpdateResult result = future.get();
                    if (result.isSuccess()) {
                        successCount++;
                        log.info("更新图书热度分数成功 - 图书ID: {}, 新分数: {}", 
                                result.getBookId(), result.getScore());
                    } else {
                        failCount++;
                        log.error("更新图书热度分数失败 - 图书ID: {}, 错误: {}", 
                                result.getBookId(), result.getErrorMessage());
                    }
                } catch (Exception e) {
                    failCount++;
                    log.error("获取更新结果失败", e);
                }
            }

            log.info("图书热度更新任务执行完成 - 成功: {}, 失败: {}", successCount, failCount);
        } catch (Exception e) {
            log.error("图书热度更新任务执行失败", e);
        }
    }

    /**
     * 计算单本图书的热度分数
     */
    private double calculateBookScore(Books book) {
        Date thirtyDaysAgo = getDateBefore(30);
        double score = 0.0;

        // 获取所有行为并计算分数
        List<Useractions> actions = QueryChain.of(useractionsMapper)
                .eq(Useractions::getBookId, book.getBookId())
                .gt(Useractions::getActionTime, thirtyDaysAgo)
                .list();

        for (Useractions action : actions) {
            score += UserActionConstant.getWeight((String) action.getActionType());
        }

        // 3. 计算评分权重
        List<UserBookRatings> ratings = QueryChain.of(userBookRatingsMapper)
                .eq(UserBookRatings::getBookId, book.getBookId())
                .gt(UserBookRatings::getCreateTime, thirtyDaysAgo)
                .list();
        
        if (!ratings.isEmpty()) {
            double avgRating = ratings.stream()
                    .mapToInt(UserBookRatings::getRating)
                    .average()
                    .orElse(0.0);
            score += avgRating * 3;
        }

        // 4. 时间衰减因子
        double timeDecay = calculateTimeDecay(book.getPublishDate());
        score *= timeDecay;

        // 5. 确保分数在0-100范围内
        return Math.min(100, Math.max(0, score));
    }

    /**
     * 获取指定天数前的日期
     */
    private Date getDateBefore(int days) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_MONTH, -days);
        return calendar.getTime();
    }

    /**
     * 计算时间衰减因子
     * 图书越新，衰减因子越大
     */
    private double calculateTimeDecay(Date publishDate) {
        if (publishDate == null) {
            return 0.5; // 默认衰减因子
        }

        long daysSincePublish = ChronoUnit.DAYS.between(
            publishDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
            LocalDate.now()
        );

        // 使用对数衰减，避免太老的图书分数过低
        return Math.max(0.5, 1.0 - Math.log10(daysSincePublish + 1) * 0.1);
    }

    /**
     * 更新结果类
     */
    @Data
    @AllArgsConstructor
    private static class UpdateResult {
        private Integer bookId;          // 图书ID
        private double score;            // 热度分数
        private boolean success;         // 是否更新成功
        private String errorMessage;     // 错误信息
    }
} 